package hr.fer.apr.ht41337.lab1;

import java.io.*;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.util.ArrayList;

public class Matrica {
	
	public final int rows;
	public final int columns;
	private double[][] matrica;
	
	public Matrica(int rows, int columns)
	{
		this.rows = rows;
		this.columns = columns;
		matrica = new double[rows][columns];
	}
	
	public Matrica(Matrica original)
	{
		rows = original.rows;
		columns = original.columns;
		matrica = new double[rows][columns];
		for (int i = 0; i < rows; i++)
			for (int j = 0; j < columns; j++)
				matrica[i][j] = original.get(i,j);
	}
	
	
	public void add(Matrica addend)
	{
		for (int i = 0; i < rows; i++)
			for (int j = 0; j < columns; j++)
				this.matrica[i][j] += addend.get(i, j);
	}
	
	
	@Override
	public Matrica clone(){
		Matrica copy = new Matrica(rows, columns);
		for (int i = 0; i < rows; i++)
		{
			for (int j = 0; j < columns; j++)
			{
				copy.matrica[i][j] = matrica[i][j];
			}
		}
		return copy;
	}
	
	
	@Override
	public boolean equals(Object o)
	{
		if (!(o instanceof Matrica))
			return false;
		
		Matrica m = (Matrica)o;
		
		if ((m.rows != this.rows) || (m.columns != this.columns))
			return false;
		for (int i = 0; i < rows; i++)
			for (int j = 0; j < columns; j++)
			{
				if (m.matrica[i][j] - matrica[i][j] > 1E-5)
					return false;
			}
		return true;
			
	}
	

	public double get(int row, int column)
	{
		if ((row >= rows) || (column >= columns))
			throw new RuntimeException("Illegal matrix dimensions.");
		else
			return matrica[row][column];
	}
	
	
	public void LUDecomposition() throws Exception
	{
		if (rows != columns)
			throw new Exception("Not a square matrix");
		
		for (int i = 0; i < rows-1; i++)
		{
				for (int j = i+1; j < rows; j++)
				{
					if (Math.abs(matrica[i][i]) < 1E-6)
						throw new Exception("Matrix doesn't admit LU decomposition");
					matrica[j][i] /= matrica[i][i];
					for (int k = i+1; k < rows; k++)
					{
						matrica[j][k] -= matrica[j][i] * matrica[i][k];
					}
				}
		}
	}
	
	
	public Matrica LUPDecomposition() throws Exception
	{
		if (rows != columns)
			throw new Exception("Not a square matrix");
		
		int[] p = new int[rows];
		for (int i = 0; i < rows; i++)
			p[i] = i;
		
		for (int i = 0; i < columns-1; i++)
		{
			for (int j = i+1; j < rows; j++)
			{
				if (Math.abs(matrica[j][i]) > Math.abs(matrica[i][i]))
				{
					switchRows(i,j);
					zamijeni(p,i,j);
				}
			}
			if (Math.abs(matrica[i][i]) < 1E-6)
				throw new Exception("Matrix doesn't admit LUP decomposition");
			
			for (int j = i+1; j < rows; j++)
			{
				matrica[j][i] /= matrica[i][i];
				for (int k = i+1; k < rows; k++)
				{
					matrica[j][k] -= matrica[j][i] * matrica[i][k];
				}
			}
		}
		if (Math.abs(matrica[rows-1][columns-1]) < 1E-6)
			throw new Exception("Matrix doesn't admit LUP decomposition");

		Matrica m = new Matrica(rows,columns);
		for (int i = 0; i < rows; i++)
			m.set(i, p[i], 1);
		
		return m;
	}
	
	
	private void zamijeni(int[] p, int i, int j)
	{
		int temp = p[i];
		p[i] = p[j];
		p[j] = temp;
	}
	
	
	public Matrica minus(Matrica B)
	{
		if ((B.rows != rows) || (B.columns != columns))
			throw new RuntimeException("Illegal matrix dimensions.");
		
		Matrica diff = new Matrica(rows, columns);
		double diffValue;
		for (int i = 0; i < rows; i++)
			for (int j = 0; j < columns; j++)
			{	
				diffValue = matrica[i][j] - B.get(i,j);
				diff.set(i, j, diffValue);
			}
		return diff;
	}

	
	public void multiply(double factor)
	{
		for (int i = 0; i < rows; i++)
			for (int j = 0; j < columns; j++)
				matrica[i][j] *= factor; 
	}
	

	public Matrica plus(Matrica addend)
	{
		if ((addend.rows != rows) || (addend.columns != columns))
			throw new RuntimeException("Illegal matrix dimensions.");
		
		Matrica sum = new Matrica(rows, columns);
		double sumValue;
		for (int i = 0; i < rows; i++)
			for (int j = 0; j < columns; j++)
			{	
				sumValue = matrica[i][j] + addend.get(i,j);
				sum.set(i, j, sumValue);
			}
		return sum;
	}
	
	
	public void print()
	{
		DecimalFormat df = new DecimalFormat("#.####");
		DecimalFormatSymbols dfs = new DecimalFormatSymbols();
		dfs.setDecimalSeparator('.');
		df.setDecimalFormatSymbols(dfs);
		String s;
		for (int i = 0; i < rows; i++)
		{
			for (int j = 0; j < columns - 1; j++)
			{
				s = df.format(matrica[i][j]);
				if (s.equals("-0"))
					s = "0";
				System.out.print(s + " ");
			}
			s = df.format(matrica[i][columns - 1]);
			if (s.equals("-0"))
				s = "0";
			System.out.println(s);
		}
		System.out.println();
	}
	
	public void print(File f)
	{
	    try {
	        BufferedWriter out = new BufferedWriter(new FileWriter(f));
	        DecimalFormat df = new DecimalFormat("#.####");
	        DecimalFormatSymbols dfs = new DecimalFormatSymbols();
			dfs.setDecimalSeparator('.');
			df.setDecimalFormatSymbols(dfs);
	        for (int i = 0; i < rows; i++)
			{
				for (int j = 0; j < columns - 1; j++)
				{
					out.write(df.format(matrica[i][j]) + " ");
				}
				out.write(df.format(matrica[i][columns - 1]));
				out.newLine();
			}
	        out.close();
	    } catch (IOException e) {
	    	e.printStackTrace();
	    }
	}
	
	
	public void print(String s)
	{
		print(new File(s));
	}

	
	public static Matrica read(File f)
	{
		int ncolumns;
		ArrayList<String[]> list = new ArrayList<String[]>();
		try 
		{
			BufferedReader input =  new BufferedReader(new FileReader(f));
			String line = input.readLine();
			String[] row = line.split("\\s");
			ncolumns = row.length;
			list.add(row);
		    while ((line = input.readLine()) != null)
		    {
		    	row = line.split("\\s");
		    	if (row.length != ncolumns)
		    		throw new IOException("Not a matrix file");
		    	list.add(row);
		    }
		}catch (IOException e)
		{
			e.printStackTrace();
			System.exit(1);
			return null;
		}
		
		Matrica matrix = new Matrica(list.size(), ncolumns);
		
		int i=0;
		for (String[] row : list)
		{
			for(int j = 0; j < row.length; j++)
			{
				matrix.set(i, j, Double.parseDouble(row[j]));
			}
			i++;
		}
		return matrix;	
	}
	
	
	public static Matrica read(String s)
	{
		return read(new File(s));
	}
	

	public void set(int row, int column, double value)
	{
		if ((row >= rows) || (column >= columns))
			throw new RuntimeException("Illegal matrix dimensions.");
		matrica[row][column] = value;
	}
	
	
	public Matrica substitutionBackward(Matrica y)
	{
		if (y.rows != this.rows)
			throw new RuntimeException("Illegal matrix dimensions.");
		if (rows != columns)
			throw new RuntimeException("Not a square matrix");
		
		Matrica x = new Matrica(y);
		
		for (int i = rows-1; i >= 0; i--)
		{
			x.matrica[i][0] /= matrica[i][i];
			for (int j = 0; j < i; j++)
			{
				x.matrica[j][0] -= matrica[j][i] * x.matrica[i][0];
			}
		}
		
		return x;
	}
	
	
	public Matrica substitutionForward(Matrica b)
	{
		if (b.rows != this.rows)
			throw new RuntimeException("Illegal matrix dimensions.");
		if (rows != columns)
			throw new RuntimeException("Not a square matrix");
		
		Matrica y = new Matrica(b);
		
		for (int i = 0; i < rows - 1; i++)
		{
			for (int j = i + 1; j < rows; j++)
			{
				y.matrica[j][0] -= matrica[j][i] * y.matrica[i][0];
			}
		}
		return y;
	}
	
	
	public void subtract(Matrica B)
	{
		for (int i = 0; i < rows; i++)
			for (int j = 0; j < columns; j++)
				this.matrica[i][j] -= B.get(i, j);
	}
	
	
	public void switchColumns(int column1, int column2)
	{
		double temp;
		for (int i = 0; i < rows; i++)
		{
			temp = matrica[i][column1];
			matrica[i][column1] = matrica[i][column2];
			matrica[i][column2] = temp;
		}
	}
	
	public void switchRows(int row1, int row2)
	{
		double temp;
		for (int i = 0; i < columns; i++)
		{
			temp = matrica[row1][i];
			matrica[row1][i] = matrica[row2][i];
			matrica[row2][i] = temp;
		}
	}
	
	
	public Matrica times(double factor)
	{
		Matrica result = new Matrica(this.rows, this.columns);
		
		for(int i = 0; i < this.rows; i++)
			for (int j = 0; j < this.columns; j++)
				result.matrica[i][j] = this.matrica[i][j] *= factor;
		
		return result;
	}
	
	
	public Matrica times(Matrica factor)
	{
		if (this.columns != factor.rows) 
			throw new RuntimeException("Illegal matrix dimensions.");
		Matrica result = new Matrica(this.rows, factor.columns);
		double resultValue;
			for(int i = 0; i < this.rows; i++)
			{
				for (int j = 0; j < factor.columns; j++)
				{
					resultValue = 0;
					for (int k = 0; k < this.columns; k++)
					{
						resultValue += this.matrica[i][k] * factor.get(k, j);
					}
					result.set(i, j, resultValue);
				}
			}
		return result;
	}

	
	public Matrica transpose() {
        Matrica transposed = new Matrica(columns, rows);
        for (int i = 0; i < rows; i++)
            for (int j = 0; j < columns; j++)
                transposed.set(j, i, this.matrica[i][j]);
        return transposed;
    }
}
